﻿/* 
 * Copyright 2010 Justin Pealing
 * 
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 * 
 *     http://www.apache.org/licenses/LICENSE-2.0
 *     
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License. 
 */

using System;
using System.Collections.Generic;
using System.IO;
using System.Net;
using Minnow.Web.Http;
using System.Web;
using System.Text;

namespace Minnow.Web.CGI
{
    /// <summary>
    /// Helper functions for working with CGI based applications
    /// </summary>
    internal static class CGIUtils
    {
        /// <summary>
        /// Gets a list of environment variabes that should be set for a HTTP request.
        /// </summary>
        /// <param name="Request">Request for which CGI environment variables are required.</param>
        /// <returns>CGI Environment variables.</returns>
        public static Dictionary<string, string> GetEnvironmentVariables(IHttpRequest Request, string FilePath)
        {
            Dictionary<string, string> returnValue = new Dictionary<string, string>();

            returnValue["CONTENT_LENGTH"] = Request.ContentLength.ToString();
            returnValue["CONTENT_TYPE"] = Request.ContentType;

            // TODO: Understand Path Info
            returnValue["PATH_INFO"] = Request.Url.AbsolutePath;
            returnValue["PATH_TRANSLATED"] = FilePath;
            returnValue["REQUEST_METHOD"] = Request.HttpMethod;
            returnValue["SCRIPT_NAME"] = Request.Url.LocalPath;
            returnValue["SCRIPT_FILENAME"] = FilePath;
            returnValue["HTTP_USER_AGENT"] = Request.UserAgent;
            returnValue["GATEWAY_INTERFACE"] = @"CGI/1.1";
            returnValue["HTTP_HOST"] = Request.Url.Host;
            returnValue["ORIG_PATH_INFO"] = Request.Url.LocalPath;
            returnValue["REMOTE_ADDR"] = Request.UserHostName;

            // TODO: Is this needed?
            returnValue["REDIRECT_STATUS"] = "200";

            string queryString = Request.Url.Query;
            if (queryString.Length > 0 && queryString[0] == '?')
            {
                queryString = queryString.Substring(1);
            }
            returnValue["QUERY_STRING"] = queryString;

            string cookies = string.Empty;
            foreach (Cookie cookie in Request.Cookies)
            {
                cookies += cookie.Name + "=" + cookie.Value + ";";
            }
            returnValue["COOKIE"] = cookies;

            return returnValue;
        }

        /// <summary>
        /// Parses the response from a CGI based application.
        /// </summary>
        /// <param name="Response">Response object to populate with the CGI response.</param>
        /// <param name="StandardOutput">Standard output stream of the CGI application.</param>
        /// <param name="StandardError">Standard error stream of the CGI application.</param>
        public static void ParseCgiResponse(IHttpResponse Response, StreamReader StandardOutput, StreamReader StandardError, int ExitCode)
        {
            string line = null;
            bool inHeader = true;
            StringBuilder header = new StringBuilder(); 
            while (inHeader && (line = StandardOutput.ReadLine()) != null)
            {
                if (line == string.Empty)
                {
                    inHeader = false;
                }
                else
                {
                    header.Append(header);
                    // TODO: What if there is an error?
                    ParseHeader(line, ref Response);
                }
            }

            using (StreamWriter writer = new StreamWriter(Response.OutputStream))
            {
                if (inHeader || ExitCode != 0)
                {
                    writer.Write("<html><body><h1>CGI Error</h1><p>");
                    writer.Write("<h3>(Exit code: '" + ExitCode + "')</h3>");

                    if (inHeader)
                    {
                        writer.Write("<h3>(Cannot parse HTTP headers!)</h3>");
                    }

                    writer.Write("<h2>STDERR</h2><hr /><pre>");
                    writer.Write(HttpUtility.HtmlEncode(StandardError.ReadToEnd()));
                    writer.Write("</pre><hr />");
                    writer.Write("<h2>STDOUT</h2><hr /><pre>");
                    writer.Write(HttpUtility.HtmlEncode(header.ToString()));
                    writer.Write(HttpUtility.HtmlEncode(StandardOutput.ReadToEnd()));
                    writer.Write("</pre><hr />");
                    writer.Write("</p></body></html>");

                    Response.StatusCode = 500;
                }
                else
                {
                    string content = StandardOutput.ReadToEnd();
                    writer.Write(content);
                }
            }

            Response.OutputStream.Close();
        }

        /// <summary>
        /// Parses a HTTP header.
        /// </summary>
        /// <param name="Line">A single HTTP header to parse.</param>
        /// <param name="Response">Response object to populate with headers.</param>
        private static void ParseHeader(string Line, ref IHttpResponse Response)
        {
            string[] keyValue = Line.Split(new char[] { ':' }, 2);
            if (keyValue.Length >= 2)
            {
                string headerName = keyValue[0].Trim().ToLower();
                string headerValue = keyValue[1].Trim();
                Response.AddHeader(headerName, headerValue);

                if (headerName == "status")
                {
                    ParseStatusCode(headerValue, Response);
                }
            }
        }

        /// <summary>
        /// Parses the status code.
        /// </summary>
        /// <param name="headerValue">The value of the Status HTTP header.</param>
        /// <param name="Response">Response object on which to populate the http status.</param>
        private static void ParseStatusCode(string HeaderValue, IHttpResponse Response)
        {
            int statusCode;
            string[] status = HeaderValue.Split(new char[] { ' ' }, 2);

            if (int.TryParse(status[0], out statusCode))
            {
                Response.StatusCode = statusCode;
            }
            if (status.Length >= 2)
            {
                Response.StatusDescription = status[1];
            }
        }
    }
}
